<?php

// 编码类

class Encode{                  

    // 适合把中文用base64编码用于url传输(比url编码短,且都是安全字符),获取时用这个来解码
    // $de : 0-编码, 1-解码, a-解码返回数组
    static function urlBase64($s, $de=0, $chrs=''){
        $a2 = $chrs ? $chrs : "-_"; // "-_" , ".~"; //《!()+,-.;@^_`~》安全13个
        if(is_array($s)) $s = implode('', $s);
        if($de){
            $s = str_pad(strtr($s,$a2,"+/"),strlen($s)%4,'=',STR_PAD_RIGHT);
            $s = base64_decode($s);
            if($de=='a') $s = Basic::strArr($s); 
        }else{
            $s = base64_encode($s);
            $s = rtrim(strtr($s,"+/",$a2),'=');
        }
        return $s;
    }
    // urlEncode,不转化中文 : 《:/?=&#%》URL 《"&<>》HTML
    static function urlEncode($str, $ext=0, $percent=0){ //url,ext,percent
        if($percent){
            $str = str_replace('%','%25',$str);
        } 
        $a = array( '#'   , '&' ); 
        $b = array( '%23' , '%26' ); 
        $str = str_replace($a, $b, $str);
        $c = array( '+'   , ' ' , '"'   ,"'"   , '<'   , '>'   , "\r"  , "\n"  , "\\"  );
        $d = array( '%2B' , '+' , '%22' ,'%27' , '%3C' , '%3E' , '%0D' , '%0A' , '%5C' );
        if($ext){
            $str = str_replace($c, $d, $str);
        }
        return $str;
    }
    
    // csv-读
    static function csvGets($file) {
        $handle = fopen($file,'r'); 
        $arr = array();
        while ($data = fgetcsv($handle)) { //每次读取CSV里面的一行内容  
            $arr = $data; //此为一个数组，要获得每一个数据，访问数组下标即可  
        }  
        fclose($handle); 
        return $arr;
    }
    // csv-写
    static function csvPuts($file, $data) {
        $handle = fopen($file, 'r+');
        foreach ($data as $line) {
            fputcsv($handle, $line);
        }
        fclose($handle);
    }

    // xml(node)-解析
    static function nodeParse($data, $cset='', $nobj='', $opt=LIBXML_NOCDATA) {
        if(is_string($data)){
            $hfix = $cset ? "<?xml version='1.0' encoding='$cset'?>" : '';
            $data = simplexml_load_string($hfix.$data, null, $opt); // obj(自动转:utf-8)
        }
        $json = json_encode($data);
        if($nobj==''){ // 空对象转化为空字符串
            $json = str_replace(['":{},','":{}}'], ['":"",','":""}'], $json);
        }
        $arr = json_decode($json, true);
        return $arr;
    }

    // 反序列化（将某些特殊字符的utf8编码转为asc码解析）不建议使用原生的unserialize。
    static function serEncode($str) { 
        $str = str_replace("\r", "", $str);
        #$str = preg_replace('/s:(\d+):"(.*?)";/se', '"s:".strlen("$2").":\"$2\";"', $str);
        $str = preg_replace_callback('/s:(\d+):"(.*?)";/s', function($arr){
            return 's:'.strlen($arr[2]).':"'.$arr[2].'";';
        }, $str);
        return unserialize($str); 
    }

    # uncode-编码 fix: %u, &#
    private function uniEncode($str, $fix='%u'){
        preg_match_all('/./u', $str, $matches);
        $reStr = "";
        foreach($matches[0] as $m){
            $itm = bin2hex(iconv('UTF-8',"UCS-4",$m));
            $reStr .= $fix.strtoupper(substr($itm,4));
        }
        return $reStr;
    }

    // unicode -解码
    static function uniDecode($str, $type='u'){
        if($type=='u'){ // '\u4E00'
            $reg = '/\\\\u([0-9a-f]{4})/i';
        }else{ // '&#x4E00'
            $reg = '/\&\#x([0-9a-f]{4})\;/i';
        }
        $str = preg_replace_callback($reg, function($arr){
            return mb_convert_encoding(pack('H*', $arr[1]), 'UTF-8', 'UCS-2BE');
        },  $str);
        return $str;
    }

    # ===============================

    // 密码加密
    static function sysPass($uid='', $upw='', $mod=''){
        if(empty($uid) || empty($upw)) return '(null)';
        return self::sysEncode("$upw@$uid@$mod", 'site', 24);
    }
    // 系统加密-混合
    // $ukey = in_array($ukey,array('site','api','other'))
    static function sysEncode($str, $ukey='site', $len=16){
        global $_cbase; $safe = $_cbase['safe'];
        $str = md5($str);
        $ukey = isset($safe[$ukey]) ? 'other' : $ukey;
        $ostr = $safe[$ukey]."@$str";
        $str = sha1($ostr).hash('ripemd128',$ostr); // 72位
        $len || $len = 16;
        return substr($str, (strlen($str)-$len)/2, $len); 
    }
    
    // 加密解密，$key：密钥；$de：是否解密；$expire 过期时间
    static function sysRevert($str, $de=0, $key='', $exp=0){
        global $_cbase; $key || $key = $_cbase['safe']['site']; 
        $nn = 4; $key = md5($key); 
        $keya = md5(substr($key,0,16)); $keyb = md5(substr($key,8,16));
        if($de){
            $str = str_replace(['-','_'], ['+','/'], $str);
            $keyc = substr($str, 0, $nn); 
            $str =  base64_decode(substr($str,$nn));
            $res = self::sysRevrnd($str, $keya, $keyc);
            $ires = intval(substr($res,0,10));
            return (!$ires || $ires-$_SERVER["REQUEST_TIME"]>0) ? @unserialize(substr($res,26)) : '';
        }else{
            $str = serialize($str); 
            $keyc = substr(md5($_SERVER["REQUEST_TIME"]), -$nn);
            $str =  sprintf('%010d', $exp ? $exp+$_SERVER["REQUEST_TIME"] : 0).substr(md5($str.$keyb),0,16).$str; 
            $res = self::sysRevrnd($str, $keya, $keyc);
            $res = base64_encode($res); 
            return $keyc.str_replace(['+','/','='], ['-','_',''], $res);
        }
    }
    // 加密解密-主算法
    static function sysRevrnd($str, $keya, $keyc){
        $res = ''; $rnd = []; $mm = strlen($str); $box = range(0,255); 
        $ckey = $keya.md5($keya.$keyc); $len = strlen($ckey);
        for($i=0; $i<=255; $i++){ $rnd[$i] = ord($ckey[$i % $len]); }
        for($j=$i=0; $i<256; $i++) {
            $j = ($j + $box[$i] + $rnd[$i]) % 256;
            $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp;
        }
        for($a=$j=$i=0; $i<$mm; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp;
            $res .= chr(ord($str[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }
        return $res;
    }

    # === jwt ==================================

    // jwt头部
    static $jwtHead = array(
        'alg' => 'HS256', //生成signature的算法
        'typ' => 'JWT'    //类型
    );
    // HMACSHA256签名 https://jwt.io/  中HMACSHA256签名实现
    static function jwtSign($input, $alg='HS256'){
        global $_cbase; $key = $_cbase['safe']['api'];
        $calg = ['HS256'=>'sha256'];
        $res = hash_hmac($calg[$alg], $input, $key, true);
        return self::jwtBase64($res);
    }
    // jwt-base64
    static function jwtBase64($input, $de=0){
        return self::urlBase64($input, $de); # urlBase64|sysRevert
    }
    // jwt-加密解密
    static function jwt($data){
        if(is_array($data)){
            $token = self::jwtMake($data);
            return strrev($token);
        }else{
            $token = strrev($data);
            return self::jwtCheck($token);
        }
    }
    /** jwt-生成token
     * $data = [ // jwt载荷: 格式如下非必须
     *  'iss' => 'jwt_admin',  //该JWT的签发者
     *  'iat' => time(),  //签发时间
     *  'exp' => time()+7200,  //过期时间
     * ]
     */
    static function jwtMake($data, $rea=0){
        if(!is_array($data)){ return false; }
        if(!isset($data['exp'])){ $data['exp'] = time()+7200; }
        $enchd = self::jwtBase64(json_encode(self::$jwtHead, JSON_UNESCAPED_UNICODE));
        $encpl = self::jwtBase64(json_encode($data, JSON_UNESCAPED_UNICODE));
        $sign = self::jwtSign("$enchd.$encpl", self::$jwtHead['alg']);
        if($rea){ return [$enchd,$encpl,$sign]; }
        $token = "$enchd.$encpl.$sign";
        return $token;
    }
    // jwt-验证token是否有效, 默认验证exp,iat时间
    static function jwtCheck($token){
        $items = is_array($token) ? $token : explode('.', $token);
        if (count($items) != 3) return false; // '3seg: Wrong token Segments'
        list($enchd, $encpl, $sign) = $items;
        //获取jwt算法
        $dechd = json_decode(self::jwtBase64($enchd,1), 1);
        if (empty($dechd['alg'])) return false; // head: Invalid header encoding
        //签名验证
        if (self::jwtSign("$enchd.$encpl", $dechd['alg']) !== $sign) return false; // head: Invalid body encoding
        $data = json_decode(self::jwtBase64($encpl,1), 1);
        //签发时间大于当前服务器时间验证失败
        if (isset($data['iat']) && $data['iat'] > time()) return false;
        //过期时间小宇当前服务器时间验证失败
        if (isset($data['exp']) && $data['exp'] < time()) return false;
        return $data;
    }
    
    # === Conv-转化类 ============================

    // 自动转换字符集 支持数组转换
    static function autoCSet($str, $from='gbk', $to='utf-8'){
        if(empty($str) || empty($from) || empty($to)) return $str;
        $from = strtoupper($from)=='UTF8'? 'utf-8':$from;
        $to = strtoupper($to)=='UTF8'? 'utf-8':$to;
        if( strtoupper($from) === strtoupper($to) || (is_scalar($str) && !is_string($str)) ){
            return $str; //如果编码相同或者非字符串标量则不转换
        }
        if(is_string($str) ) {
            if(function_exists('iconv')){
                return iconv($from,$to."//IGNORE",$str); 
            }elseif(function_exists('mb_convert_encoding')){
                return mb_convert_encoding ($str, $to, $from);
            }else{
                return $str;
            }
        }elseif(is_array($str)){
            foreach ( $str as $key => $val ) {
                $str[$key] = self::autoCSet($val, $from, $to);
                // 不考虑key转化
            }
            return $str;
        }else{
            return $str;
        }
    }
    
    # ===============================

    // 简繁对照表
    static function jfTab(){
        static $jfcfg_tab;
        if(!isset($jfcfg_tab)){
            $file = DIR_DATA.'/xtab/jianfan.imp_txt';
            $tmp = Fsop::get($file);
            $tmp = explode('(split!@~flag)',$tmp);
            $jfcfg_tab[1] = trim($tmp[1]);
            $jfcfg_tab[2] = trim($tmp[2]);
        }
        return $jfcfg_tab;
    }
    // 简繁转化
    static function jfMain($str, $type='j2f', $cset='3'){
        $jf = ""; $p = 0; $len = strlen($str);
        $tab = self::jfTab();
        $tab1 = $tab[$type=='j2f'?1:2];
        $tab2 = $tab[$type=='j2f'?2:1];
        for($i=0;$i<$len;$i++){   
            $ch = substr($str, $p, 1);
            if(ord($ch)<128){ 
                $jf .= $ch; $p++;
            }else{
                $ch = substr($str, $p, $cset);
                $tp = strpos($tab1, ",$ch"); 
                if($tp) $ch = substr($tab2, $tp+1, $cset);
                $jf .= "$ch"; $p += $cset; 
            }
            if($p>=$len) break;
        }   
        return $jf; 
    }
    
    # ===============================

    // 拼音对照表
    static function pyTab(){
        static $pycfg_tab;
        if(!isset($pycfg_tab)){
            $file = DIR_DATA.'/xtab/pinyin.imp_txt';
            $pycfg_tab = Fsop::get($file);
        }
        return $pycfg_tab;
    }
    // 拼音转化-单个汉字
    static function pyOne($chr){
        $tab = self::pyTab();
        $p = strpos($tab, $chr); 
        $t = substr($tab, 0, $p);
        $t = strrchr($t, "(");
        $p = strpos($t, ")");
        $t = substr($t, 1, $p-1);
        return $t;   
    }
    // 拼音转化 (eg:内页广告: 0-neiyeguanggao, 1-n, 9-nygg)
    static function pyMain($str ,$first=0, $cset='3'){
        $cset = $cset=='3' ? 'utf-8' : 'gbk';
        $arr = Basic::strArr($str, $cset);
        $len = count($arr); $py=""; 
        for($i=0;$i<$len;$i++){
            if(ord($arr[$i])<128){
                $py .= preg_replace("/\W/", '_', $arr[$i]);
            }else{
                $tmp = self::pyOne($arr[$i]); 
                $py .= $first ? substr($tmp,0,1) : $tmp;
            }
            if($first=='1' && $py) return substr($py,0,1); 
        }
        return $py; 
    }

    # ===============================

    // 获取一个 markdown 内容
    static function mdOne($tpl, $ext='.md'){
        $ext || $ext = '.md';
        $tpf = tpl_path($tpl, $ext);
        if(file_exists(DIR_THEME."/$tpf")){
            $data = file_get_contents(DIR_THEME."/$tpf");
        }else{
            $data = "<!--(Markdown `$tpf` NOT Found!)-->";
        }
        return $data;
    }
    // 显示解析 markdown 内容
    static function mdView($stpl, $retitle=0){
        global $_cbase; $mkvs = $_cbase['mkvs'];
        $tab = ['mod','key','view'];
        foreach($tab as $tk){ $stpl = str_replace('{'.$tk.'}',$mkvs[$tk],$stpl); }
        if(preg_match("/^[\w\.\-\~\@\!\/]+$/i", $stpl)){
            $stpl = self::mdOne($stpl);
        } // _.- ~@!
        if($retitle){
            preg_match("/([#]+) ([^\n\r]+)/i", $stpl, $match); 
            return empty($match[2]) ? '' : $match[2];
        }
        preg_match_all("/{(inc|md):\"([\w\.\-\~\@\!\/]+)\"}/i", $stpl, $match); 
        if(count($match[2])>0){ 
            $arr = $match[2]; 
            foreach($arr as $k0=>$tpl){ 
                $mstr = $match[0][$k0];
                $mkey = $match[1][$k0];
                $ext = strchr($tpl,'.'); 
                $ptpl = self::mdOne($tpl, $ext);
                $stpl = str_replace($mstr, $ptpl, $stpl);
            }
        }
        if(preg_match("/{(inc|md):\"([\w\.\-\~\@\!\/]+)\"}/i", $stpl)){ return self::mdView($stpl); }
        $stpl = fix_link($stpl); // strpos($stpl,'{md:"')
        include_once DIR_VENDOR."/sinclass/Parsedown.php";
        $md = new \Parsedown();
        $data = $md->text($stpl);
        return $data;
    }

    # ===================================== 
    
    // 创建rsa公私密钥对
    static function rsapk($cfg=[]){ 
        $config = array(
            //"digest_alg" => "sha512",
            "private_key_bits" => 1024, //字节数：512, 1024, 2048, 4096 等
            "private_key_type" => OPENSSL_KEYTYPE_RSA, //加密类型
        );
        if(!empty($cfg['openssl_cnf']) && file_exists($cfg['openssl_cnf'])){
            $config['config'] = $cfg['openssl_cnf'];
        }
        //创建公私钥
        $res = openssl_pkey_new($config);
        //获取私钥
        $passKey = empty($cfg['pass']) ? null : $cfg['pass'];
        openssl_pkey_export($res, $private_key, $passKey, $config);
        //获取公钥
        $public_key = openssl_pkey_get_details($res)['key'];
        //组合rsa
        $rsa = [
            'public_key' => $public_key,
            'private_key' => $private_key,
        ];
        return $rsa;
    }

}

